<?php
/**
 * @Author: laoweizhen <1149243551@qq.com>,
 * @Date: 2022/10/06 16:59,
 * @LastEditTime: 2022/10/06 16:59
 */
declare(strict_types=1);

namespace Zhen\HyperfKit\Traits;

use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Collection;
use Hyperf\DbConnection\Db;
use Zhen\HyperfKit\Constants\DBConst;
use Zhen\HyperfKit\CoreModel;
use Zhen\HyperfKit\Exception\CoreException;

trait DaoTrait
{
    public string $model;



    /**
     * 批量插入或更新表中数据（有主从复制，慎用）
     *
     * @param array $data 要插入的数据，元素中的key为表中的column，value为对应的值
     * @return array
     */
    public function batchInsertOrUpdate(array $data): array
    {

        if (empty($data)) {//如果传入数据为空 则直接返回
            return [
                'insertNum' => 0,
                'updateNum' => 0
            ];
        }

        $model = (new $this->model);
        $table = $model->getTable();
        $dbConnectionName = $model->getConnectionName();
        $columns = array_keys($data[0]);

        //拼装sql
        $sql = "insert into " . $table . " (";
        foreach ($columns as $k => $column) {
            $sql .= $column . " ,";
        }
        $sql = trim($sql, ',');
        $sql .= " ) values ";

        foreach ($data as $k => $v) {
            $sql .= "(";
            foreach ($columns as $column) {
                if ('updated_at' == $column) { //如果库中存在，create_at字段会被更新
                    $sql .= " '" . date('Y-m-d H:i:s') . "' ,";
                } else {
                    $val = ''; //插入数据中缺少$colums中的字段时的默认值
                    if (isset($v[$column])) {
                        $val = $v[$column];
                        $val = addslashes($val);  //在预定义的字符前添加反斜杠的字符串。
                    }
                    $sql .= " '" . $val . "' ,";
                }
            }
            $sql = trim($sql, ',');
            $sql .= " ) ,";
        }
        $sql = trim($sql, ',');
        $sql .= "on duplicate key update ";
        foreach ($columns as $k => $column) {
            $sql .= $column . " = values (" . $column . ") ,";
        }
        $sql = trim($sql, ',');
        $sql .= ';';

        $columnsNum = count($data);
        $retNum = Db::connection($dbConnectionName)->update(Db::raw($sql));
        $updateNum = $retNum - $columnsNum;
        $insertNum = $columnsNum - $updateNum;
        return [
            'insertNum' => $insertNum,
            'updateNum' => $updateNum
        ];
    }

    /**
     * 获取列表数据.
     * @param array $where where查询条件
     * @param array $params
     *     select：查询的字段（数组）
     *     order_by：排序的字段。多个字段用数组
     *     order_type：排序的类型。如果指定类型，需要跟 order_by 一一对应
     *     table_alias：表别名
     *     trashed_type: 1：默认；2：获取已删除的；3：只返回已删除的
     * @param null|int $maxSize 最大数据量。传 0 不限制
     * @return Collection
     */
    public function getList(array $where, array $params, ?int $maxSize = null): Collection
    {
        is_null($maxSize) && $maxSize = $this->model::MAX_PAGE_SIZE;

        return $this->listQuerySetting($where, $params)
            ->when($maxSize > 0, function ($query) use ($maxSize) {
                return $query->limit($maxSize);
            })->get();
    }

    /**
     * 获取分页列表.
     * @param array $where 查询条件
     * @param array $params 列表参数
     *     page：页码
     *     size：分页大小
     *     select：查询的字段（数组）
     *     order_by：排序的字段。多个字段用数组
     *     order_type：排序的类型。如果指定类型，需要跟 order_by 一一对应
     *     table_alias：表别名
     *     trashed_type: 1：默认；2：获取已删除的；3：只返回已删除的
     * @param bool $getTotal 是否获取数据总数
     */
    public function getPageList(array $where, array $params, bool $getTotal = false): array
    {
        $pageNo = (int)(isset($params['page']) && $params['page'] > 0 ? $params['page'] : $this->model::DEFAULT_PAGE_NO);
        $pageSize = (int)(isset($params['size']) && $params['size'] > 0 && $params['size'] <= $this->model::MAX_PAGE_SIZE ? $params['size'] : $this->model::DEFAULT_PAGE_SIZE);

        $query = $this->listQuerySetting($where, $params);

        // 获取总数
        $total = null;
        if ($getTotal) {
            $total = $query->toBase()->getCountForPagination();
            if ($total === 0) {
                return $this->setPaginate([], $pageNo, $pageSize, $total);
            }
        }

        $list = $query->offset(($pageNo - 1) * $pageSize)
            ->limit($pageSize)
            ->get()->toArray();
        return $this->setPaginate($list, $pageNo, $pageSize, $total);
    }

    /**
     * 列表查询模型
     * @param array $params
     *     page：页码
     *     size：分页大小
     *     select：查询的字段（数组）
     *     order_by：排序的字段。多个字段用数组
     *     order_type：排序的类型。如果指定类型，需要跟 order_by 一一对应
     *     table_alias：表别名
     *     trashed_type: 1：默认；2：获取已删除的；3：只返回已删除的
     * @param array $where 查询条件
     * @return Builder
     * @author lwz
     */
    public function listQuerySetting(array $where, array $params): Builder
    {
        $query = $this->model::query()->setQueryParams($where, $params['table_alias'] ?? null);

        switch ($params['trashed_type'] ?? null) {
            case DBConst::TRASHED_TYPE_WITH:
                $query->withTrashed();
                break;
            case DBConst::TRASHED_TYPE_ONLY:
                $query->onlyTrashed();
                break;
        }

        if ($params['select'] ?? false) {
//            $query->select($this->filterQueryAttributes($params['select']));
            $query->select($params['select']);
        }
        return $this->handleOrder($query, $params);
    }

    /**
     * 处理排序
     * @param Builder $query
     * @param array $params
     *     order_by：排序的字段
     *     order_type：排序的类型
     * @return Builder
     * @author lwz
     */
    protected function handleOrder(Builder $query, array &$params): Builder
    {
        if ($params['order_by'] ?? false) {
            if (is_array($params['order_by'])) {
                foreach ($params['order_by'] as $key => $order) {
                    $query->orderBy($order, $params['order_type'][$key] ?? 'asc');
                }
            } else {
                $query->orderBy($params['order_by'], $params['order_type'] ?? 'asc');
            }
        }

        return $query;
    }

    /**
     * 获取分页
     * @param array $list
     * @param int|null $pageNo
     * @param int|null $pageSize
     * @param int|null $total
     * @return array
     * @author lwz
     */
    public function setPaginate(array $list, ?int $pageNo = null, ?int $pageSize = null, ?int $total = null): array
    {
        $totalInfo = [];
        if ($total) {
            $totalInfo = [
                'total' => $total,
                'page_count' => isset($pageSize) ? ceil($total / $pageSize) : 0,
            ];
        }

        return array_merge($totalInfo, [
            'page' => $pageNo ?? 1,
            'size' => $pageSize ?? 0,
            'list' => $list,
        ]);
    }

    /**
     * 过滤查询字段不存在的属性
     * @param array $fields
     * @param bool $removePk
     * @return array
     */
    protected function filterQueryAttributes(array $fields, bool $removePk = false): array
    {
        $model = new $this->model;
        $attrs = $model->getFillable();
        foreach ($fields as $key => $field) {
            if (!in_array(trim($field), $attrs) && mb_strpos(str_replace('AS', 'as', $field), 'as') === false) {
                unset($fields[$key]);
            } else {
                $fields[$key] = trim($field);
            }
        }
        if ($removePk && in_array($model->getKeyName(), $fields)) {
            unset($fields[array_search($model->getKeyName(), $fields)]);
        }
        $model = null;
        return (count($fields) < 1) ? ['*'] : $fields;
    }

    /**
     * 通过ID查找一条记录.
     * @param int $id id
     * @param array $fields 获取的字段
     */
    public function getOneById(int $id, array $fields = ['*']): ?CoreModel
    {
        return $this->model::query()->select($fields)->find($id);
    }

    /**
     * 通过where查找一条记录.
     * @param array|string[] $fields
     */
    public function getOne(array $where, array $fields = ['*']): ?CoreModel
    {
        return $this->model::query()->setQueryParams($where)->select($fields)->first();
    }

    /**
     * 获取数据总数.
     * @param array $where 查询条件
     */
    public function getCount(array $where): int
    {
        return $this->model::query()->setQueryParams($where)->count();
    }

    /**
     * 新增数据（一条）.
     */
    public function save(array $data): CoreModel
    {
        return $this->model::query()->create($data);
    }

    /**
     * 批量插入数据.
     */
    public function insert(array $data): bool
    {
        return $this->model::query()->insert($data);
    }

    /**
     * 更新或添加数据.
     * @param array $where 查询条件
     * @param array $data 更新的数据
     */
    public function updateOrCreate(array $where, array $data): CoreModel
    {
        return $this->model::query()->updateOrCreate($where, $data);
    }

    /**
     * 更新数据.
     * @param array $where 更新条件
     * @param array $data 更新的数据
     * @param bool $withTrashed 是否更新被删除的数据
     * @return int
     */
    public function update(array $where, array $data, bool $withTrashed = false): int
    {
        // 更新条件为空，禁止更新
        if (empty($where)) {
            return 0;
        }
        return $this->model::query()->setQueryParams($where)->when($withTrashed, function ($query) {
            return $query->withTrashed();
        })->update($data);
    }

    /**
     * 批量更新.
     * @param array $where 查询条件
     * @param array $updateData 更新的数据（更新单个字段，传一维数组；更新多个字段，传二维数组）
     *                          [
     *                          'field' => '更新的字段',
     *                          'case_data' => [
     *                          ['when_field' => '判断字段', 'when_val' => '判断的值', 'update_val' => '更新的值']
     *                          ['when_raw' => 'when的完整条件', 'update_val' => '更新的值']
     *                          ]
     *                          ]
     * @param bool $withTrashed 是否更新被删除的数据
     */
    public function updateBatch(array $where, array $updateData, bool $withTrashed = false): int
    {
        // 如果存在索引0，则视为更新多个字段
        if (isset($updateData[0])) {
            $updateArr = [];
            foreach ($updateData as $item) {
                $updateArr = array_merge($updateArr, $this->getUpdateBatchStr($item));
            }
        } else {
            $updateArr = $this->getUpdateBatchStr($updateData);
        }

        return $this->model::query()->setQueryParams($where)
            ->when($withTrashed, function ($query) {
                return $query->withTrashed();
            })
            ->update($updateArr);
    }

    /**
     * 根据 where column自增num.
     * @param array $where 查询条件
     * @param string $column 字段
     * @param int $num 自增数量
     */
    public function increment(array $where, string $column, int $num = 1): int
    {
        return $this->model::query()->setQueryParams($where)
            ->increment($column, $num);
    }

    /**
     * 根据 where column自减 num.
     * @param array $where 查询条件
     * @param string $column 字段
     * @param int $num 自增数量
     */
    public function decrement(array $where, string $column, int $num = 1): int
    {
        return $this->model::query()->setQueryParams($where)
            ->decrement($column, $num);
    }

    /**
     * 根据where 删除多条记录.
     * @param array $where 查询条件
     * @return int 删除的数据条数
     * @throws CoreException|\Exception
     */
    public function deleteByWhere(array $where): int
    {
        if (empty($where)) {
            throw new CoreException('[where] 参数错误');
        }
        return $this->model::query()->setQueryParams($where)->delete();
    }

    /**
     * 强制删除全部数据（包括软删除数据）.
     * @param array $where 查询条件
     * @return null|bool
     */
    public function forceDeleteAllData(array $where): ?bool
    {
        if (empty($where)) {
            throw new CoreException('缺少查询参数');
        }
        return $this->model::query()->setQueryParams($where)->withTrashed()->forceDelete();
    }

    /**
     * 获取更新字段字符串.
     * @param array $updateData 更新的数据
     * @throws CoreException
     */
    protected function getUpdateBatchStr(array $updateData): array
    {
        // case 参数不能为空
        if (empty($updateData['case_data'] ?? null)) {
            throw new CoreException('case_data error');
        }

        /**
         * 字符串示例：
         * SET `type` = (CASE
         * WHEN  `name` = 1 THEN 999
         * WHEN  `name` = 2 THEN 1000
         * WHEN  `name` = 3 THEN 1024
         * END).
         */
        $whereRaw = 'CASE';
        foreach ($updateData['case_data'] as $item) {
            if (isset($item['when_raw'])) {
                $whereRaw .= sprintf(' WHEN %s then %s', $item['when_raw'], $this->getSqlWhereVal($item['update_val']));
            } else {
//                $whereRaw .= " WHEN {$item['when_field']} = '{$item['when_val']}' then '{$item['update_val']}'";
                $whereRaw .= sprintf(
                    ' WHEN %s = %s then %s',
                    $item['when_field'],
                    $this->getSqlWhereVal($item['when_val']),
                    $this->getSqlWhereVal($item['update_val'])
                );
            }
        }
        $whereRaw .= ' ELSE `' . $updateData['field'] . '` END';

        return [$updateData['field'] => Db::raw($whereRaw)];
    }

    /**
     * 获取 sql 查询值
     * @param int|string $value 查询的值
     */
    protected function getSqlWhereVal(int|string $value): int|string
    {
        // 如果是字符串，加上双引号
        return is_string($value) ? '"' . $value . '"' : $value;
    }
}